#!/bin/sh

# Exit on 1st error
set -e

# NFS exports file.  Some code below keeps a cache of output derived
# from exportfs(8).  When this file is updated the cache is invalid
# and needs to be regenerated.
#
# To change the file, edit the default value below.  Do not set
# CTDB_NFS_EXPORTS_FILE - it isn't a configuration variable, just a
# hook for testing.
nfs_exports_file="${CTDB_NFS_EXPORTS_FILE:-/var/lib/nfs/etab}"

# Red Hat
nfs_service="nfs"
nfslock_service="nfslock"
nfs_config="/etc/sysconfig/nfs"

# SUSE
#nfs_service="nfsserver"
#nfslock_service=""
#nfs_config="/etc/sysconfig/nfs"

# Debian
#nfs_service="nfs-kernel-server"
#nfslock_service=""
#nfs_config="/etc/default/nfs-kernel-server"

# Override for unit testing
if [ -z "$PROCFS_PATH" ] ; then
    PROCFS_PATH="/proc"
fi

##################################################

usage ()
{
    _c=$(basename "$0")
    cat <<EOF
usage: $_c { shutdown | startup }
       $_c { stop | start } { nfs | nlockmgr }
       $_c { monitor-list-shares | monitor-post }
       $_c { register }
EOF
    exit 1
}


##################################################
# Basic service stop and start

basic_stop ()
{
    case "$1" in
	nfs)
	    service "$nfs_service" stop
	    if [ -n "$nfslock_service" ] ; then
		service "$nfslock_service" stop
	    fi
	    ;;
	nfslock)
	    if [ -n "$nfslock_service" ] ; then
		service "$nfslock_service" stop
	    else
		service "$nfs_service" stop
	    fi
	    ;;
	*)
	    usage
    esac
}

basic_start ()
{
    case "$1" in
	nfs)
	    if [ -n "$nfslock_service" ] ; then
		service "$nfslock_service" start
	    fi
	    service "$nfs_service" start
	    ;;
	nfslock)
	    if [ -n "$nfslock_service" ] ; then
		service "$nfslock_service" start
	    else
		service "$nfs_service" start
	    fi
	    ;;
	*)
	    usage
    esac
}

##################################################
# service "stop" and "start" options for restarting

service_stop ()
{
    case "$1" in
	nfs)
	    echo 0 >"${PROCFS_PATH}/fs/nfsd/threads"
	    basic_stop "nfs" >/dev/null 2>&1 || true
	    pkill -9 nfsd
	    ;;
	nlockmgr)
	    basic_stop "nfslock" >/dev/null 2>&1 || true
	    ;;
	*)
	    usage
    esac
}

service_start ()
{
    case "$1" in
	nfs)
	    basic_start "nfs"
	    ;;
	nlockmgr)
	    basic_start "nfslock"
	    ;;
	*)
	    usage
    esac
}

##################################################
# service init startup and final shutdown

nfs_shutdown ()
{
    basic_stop "nfs"
}

nfs_startup ()
{
    basic_stop "nfs" || true
    basic_start "nfs"
    _f="${PROCFS_PATH}/sys/net/ipv4/tcp_tw_recycle"
    if [ "$_f" ] ; then
	echo 1 >"$_f"
    fi
}

##################################################
# monitor-post support

nfs_check_thread_count ()
{
    # Load NFS configuration to get desired number of threads.
    if [ -r "$nfs_config" ] ; then
	. "$nfs_config"
    fi

    # If $RPCNFSDCOUNT/$USE_KERNEL_NFSD_NUMBER isn't set then we could
    # guess the default from the initscript.  However, let's just
    # assume that those using the default don't care about the number
    # of threads and that they have switched on this feature in error.
    _configured_threads="${RPCNFSDCOUNT:-${USE_KERNEL_NFSD_NUMBER}}"
    [ -n "$_configured_threads" ] || return 0

    _threads_file="${PROCFS_PATH}/fs/nfsd/threads"

    # nfsd should be running the configured number of threads.  If
    # there are a different number of threads then tell nfsd the
    # correct number.
    read _running_threads <"$_threads_file" || {
	    echo "WARNING: Reading \"${_threads_file}\" unexpectedly failed"
	    exit 0
    }

    # Intentionally not arithmetic comparison - avoids extra errors
    # when above read fails in an unexpected way...
    if [ "$_running_threads" != "$_configured_threads" ] ; then
	echo "Attempting to correct number of nfsd threads from ${_running_threads} to ${_configured_threads}"
	echo "$_configured_threads" >"$_threads_file"
    fi
}

##################################################
# list share directories

nfs_monitor_list_shares ()
{
    _cache_file="${CTDB_NFS_CALLOUT_STATE_DIR}/list_shares_cache"
    # -nt operator is well supported in Linux: dash, bash, ksh, ...
    # shellcheck disable=SC2039
    if  [ ! -r "$nfs_exports_file" ] || [ ! -r "$_cache_file" ] || \
	    [ "$nfs_exports_file" -nt "$_cache_file" ] ; then
	mkdir -p "$CTDB_NFS_CALLOUT_STATE_DIR"
	# We could just use the contents of $nfs_exports_file.
	# However, let's regard that file as internal to NFS and use
	# exportfs, which is the public API.
	if ! _exports=$(exportfs -v) ; then
	    echo "WARNING: failed to run exportfs to list NFS shares" >&2
	    return
	fi

	echo "$_exports" |
	    grep '^/' |
	    sed -e 's@[[:space:]][[:space:]]*[^[:space:]()][^[:space:]()]*([^[:space:]()][^[:space:]()]*)$@@' |
	    sort -u >"$_cache_file"
    fi

    cat "$_cache_file"
}

##################################################

nfs_register ()
{
    cat <<EOF
shutdown
startup
stop
start
monitor-list-shares
monitor-post
EOF
}

##################################################

case "$1" in
    shutdown)
	nfs_shutdown
	;;
    startup)
	nfs_startup
	;;
    stop)
	service_stop "$2"
	;;
    start)
	service_start "$2"
	;;
    monitor-list-shares)
	nfs_monitor_list_shares
	;;
    monitor-post)
	nfs_check_thread_count
	;;
    register)
	nfs_register
	;;
    monitor-pre|releaseip|takeip|releaseip-pre|takeip-pre)
	# Not required/implemented
	:
	;;
    *)
	usage
esac
